package v1

import (
  "context"
  "database/sql"
  "fmt"
  "github.com/golang/protobuf/ptypes"
  "google.golang.org/grpc/codes"
  "google.golang.org/grpc/status"
  "log"
  "runtime"
  "strtv.cn/ad-service/pkg/api/v1"
  "time"
)

const (
	// apiVersion is version of API is provided by server
	apiVersion = "v1"
)

// departmentServiceServer is implementation of v1.DepartmentServiceServer proto interface
type departmentServiceServer struct {
	db *sql.DB
}

// NewDepartmentServiceServer creates Department service
func NewDepartmentServiceServer(db *sql.DB) v1.DepartmentServiceServer {
	return &departmentServiceServer{db: db}
}

// checkAPI checks if the API version requested by client is supported by server
func (s *departmentServiceServer) checkAPI(api string) error {
	// API version is "" means use current version of the service
	if len(api) > 0 {
		if apiVersion != api {
			return status.Errorf(codes.Unimplemented,
				"unsupported API version: service implements API version '%s', but asked for '%s'", apiVersion, api)
		}
	}
	return nil
}

// connect returns SQL database connection from the pool
func (s *departmentServiceServer) connect(ctx context.Context) (*sql.Conn, error) {
	c, err := s.db.Conn(ctx)
	if err != nil {
		return nil, status.Error(codes.Unknown, "failed to connect to database-> "+err.Error())
	}
	return c, nil
}

// Create new department
func (s *departmentServiceServer) Create(ctx context.Context, req *v1.CreateRequest) (*v1.CreateResponse, error) {

  // check if the API version requested by client is supported by server
	if err := s.checkAPI(req.Api); err != nil {
		return nil, err
	}

	// get SQL connection from pool
	c, err := s.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	data := make(chan *v1.CreateResponse, 1)
	go handleCreate(ctx, req, c, data)
	select {
	case res := <-data:
		return res, nil
	case <-ctx.Done():
		return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
	}
}

func handleCreate(ctx context.Context, req *v1.CreateRequest, c *sql.Conn, data chan<- *v1.CreateResponse) {
	select {
	case <-ctx.Done():
		log.Println(ctx.Err())
		runtime.Goexit() //超时后退出该Go协程
  default:
		res, _ := create(ctx, req, c)
		// //修改数据库前进行超时判断
		// if ctx.Err() == context.Canceled{
		// 	...
		// 	//如果已经超时，则退出
		// }
		data <- res
	}
}

func create(ctx context.Context, req *v1.CreateRequest, c *sql.Conn) (*v1.CreateResponse, error) {
  var createdAt time.Time
  var err error
  if req.Department.CreatedAt != nil {
    createdAt, err = ptypes.Timestamp(req.Department.CreatedAt)
    if err != nil {
      return nil, err
    }
  } else {
    createdAt = time.Now()
  }

  var p sql.NullInt64
  if req.Department.Pid == 0 {
    p = sql.NullInt64{}
  }

  // insert Department entity data
  res, err := c.ExecContext(ctx, "INSERT INTO departments(`name`, `description`, `pid`, `status`, `created_at`, `updated_at`) VALUES(?, ?, ?, ?, ?, ?)",
    req.Department.Name, req.Department.Description, p, req.Department.Status, createdAt, createdAt)

  if err != nil {
    return nil, status.Error(codes.Unknown, "failed to insert into departments-> "+err.Error())
  }

  // get ID of creates Department
  id, err := res.LastInsertId()
  if err != nil {
    return nil, status.Error(codes.Unknown, "failed to retrieve id for created department-> "+err.Error())
  }

  return &v1.CreateResponse{
    Api: apiVersion,
    Id:  id,
  }, nil
}

// Read department
func (s *departmentServiceServer) Read(ctx context.Context, req *v1.ReadRequest) (*v1.ReadResponse, error) {
	// check if the API version requested by client is supported by server
	if err := s.checkAPI(req.Api); err != nil {
		return nil, err
	}

	// get SQL connection from pool
	c, err := s.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	// query Department by ID
	rows, err := c.QueryContext(ctx, "SELECT `id`, `name`, `description`, `pid` , `status` , `created_at` , `updated_at` FROM departments WHERE `id`=? AND `deleted_at` is NULL",
		req.Id)
	if err != nil {
		return nil, status.Error(codes.Unknown, "failed to select from departments-> "+err.Error())
	}
	defer rows.Close()

	if !rows.Next() {
		if err := rows.Err(); err != nil {
			return nil, status.Error(codes.Unknown, "failed to retrieve data from departments-> "+err.Error())
		}
		return nil, status.Error(codes.NotFound, fmt.Sprintf("department with ID='%d' is not found",
			req.Id))
	}

	// get Department data
	var dept v1.Department
	var createdAt time.Time
	var updatedAt time.Time
	var pid sql.NullInt64
	if err := rows.Scan(&dept.Id, &dept.Name, &dept.Description, &pid, &dept.Status, &createdAt, &updatedAt); err != nil {
		return nil, status.Error(codes.Unknown, "failed to retrieve field values from department row-> "+err.Error())
	}

	if pid.Valid {
		dept.Pid = pid.Int64
	} else {
		dept.Pid = 0
	}

	dept.CreatedAt, err = ptypes.TimestampProto(createdAt)
	if err != nil {
		return nil, status.Error(codes.Unknown, "created_at field has invalid format-> "+err.Error())
	}
	dept.UpdatedAt, err = ptypes.TimestampProto(updatedAt)
	if err != nil {
		return nil, status.Error(codes.Unknown, "updated_at field has invalid format-> "+err.Error())
	}
	if rows.Next() {
		return nil, status.Error(codes.Unknown, fmt.Sprintf("found multiple department rows with ID='%d'",
			req.Id))
	}

	return &v1.ReadResponse{
		Api:        apiVersion,
		Department: &dept,
	}, nil

}

// Update department
func (s *departmentServiceServer) Update(ctx context.Context, req *v1.UpdateRequest) (*v1.UpdateResponse, error) {
	// check if the API version requested by client is supported by server
	if err := s.checkAPI(req.Api); err != nil {
		return nil, err
	}

	// get SQL connection from pool
	c, err := s.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	// update Department
	var updatedAt time.Time

	if req.Department.CreatedAt != nil {
		updatedAt, err = ptypes.Timestamp(req.Department.UpdatedAt)
		if err != nil {
			return nil, err
		}
	} else {
		updatedAt = time.Now()
	}
	res, err := c.ExecContext(ctx, "UPDATE departments SET `name`=?, `description`=?, `pid`=?, `status`=?, `updated_at`=? WHERE `id`=?",
		req.Department.Name, req.Department.Description, req.Department.Pid, req.Department.Status, updatedAt, req.Department.Id)
	if err != nil {
		return nil, status.Error(codes.Unknown, "failed to update Department-> "+err.Error())
	}

	rows, err := res.RowsAffected()
	if err != nil {
		return nil, status.Error(codes.Unknown, "failed to retrieve rows affected value-> "+err.Error())
	}

	if rows == 0 {
		return nil, status.Error(codes.NotFound, fmt.Sprintf("department with ID='%d' is not found",
			req.Department.Id))
	}

	return &v1.UpdateResponse{
		Api:     apiVersion,
		Updated: rows,
	}, nil
}

// Delete department
func (s *departmentServiceServer) Delete(ctx context.Context, req *v1.DeleteRequest) (*v1.DeleteResponse, error) {
	// check if the API version requested by client is supported by server
	if err := s.checkAPI(req.Api); err != nil {
		return nil, err
	}

	// get SQL connection from pool
	c, err := s.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	// delete Department
	res, err := c.ExecContext(ctx, "DELETE FROM departments WHERE `id`=?", req.Id)
	if err != nil {
		return nil, status.Error(codes.Unknown, "failed to delete Department-> "+err.Error())
	}

	rows, err := res.RowsAffected()
	if err != nil {
		return nil, status.Error(codes.Unknown, "failed to retrieve rows affected value-> "+err.Error())
	}

	if rows == 0 {
		return nil, status.Error(codes.NotFound, fmt.Sprintf("department with ID='%d' is not found",
			req.Id))
	}

	return &v1.DeleteResponse{
		Api:     apiVersion,
		Deleted: rows,
	}, nil
}

// Read all departments
func (s *departmentServiceServer) ReadAll(ctx context.Context, req *v1.ReadAllRequest) (*v1.ReadAllResponse, error) {
	// check if the API version requested by client is supported by server
	if err := s.checkAPI(req.Api); err != nil {
		return nil, err
	}

	// get SQL connection from pool
	c, err := s.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	// get Department list
	rows, err := c.QueryContext(ctx, "SELECT `id`, `name`, `description`, `pid`, `status`, `created_at`, `updated_at` FROM departments")
	if err != nil {
		return nil, status.Error(codes.Unknown, "failed to select from departments-> "+err.Error())
	}
	defer rows.Close()

	var createdAt time.Time
	var updatedAt time.Time
	departments := []*v1.Department{}
	for rows.Next() {
		dept := new(v1.Department)
		var pid sql.NullInt64
		if err := rows.Scan(&dept.Id, &dept.Name, &dept.Description, &pid, &dept.Status, &createdAt, &updatedAt); err != nil {
			return nil, status.Error(codes.Unknown, "failed to retrieve field values from department row-> "+err.Error())
		}
		if pid.Valid {
			dept.Pid = pid.Int64
		} else {
			dept.Pid = 0
		}
		dept.CreatedAt, err = ptypes.TimestampProto(createdAt)
		if err != nil {
			return nil, status.Error(codes.Unknown, "created_at field has invalid format-> "+err.Error())
		}
		dept.UpdatedAt, err = ptypes.TimestampProto(updatedAt)
		if err != nil {
			return nil, status.Error(codes.Unknown, "updated_at field has invalid format-> "+err.Error())
		}
		departments = append(departments, dept)
	}

	if err := rows.Err(); err != nil {
		return nil, status.Error(codes.Unknown, "failed to retrieve data from departments-> "+err.Error())
	}

	return &v1.ReadAllResponse{
		Api:         apiVersion,
		Departments: departments,
	}, nil
}
